/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package hellfire.system.graphics;

import hellfire.system.engine.Main;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import java.util.Arrays;
import java.util.LinkedList;

import javax.microedition.khronos.opengles.GL10;

import android.graphics.Bitmap;
import android.opengl.GLES20;
import android.opengl.Matrix;
import android.util.Log;
import android.util.Pair;

public class GLCPUCanvas implements GLDrawable
{
	//Position & Size properties
    private GLVec2 pos, dim;
    
    //Cache for handles
    private GLCache cache = new GLCache();
    
    //do we need to rebuild the texture
    private boolean needBuildTexture;
    
    //range of lines for update
    private LinkedList<Pair<Integer, Integer>> toUpdate;
    
    //the canvas texture
    private int canvasTexture[];
    
    //the canvas buffer (in CPU memory)
    private IntBuffer canvasBuffer;
	
    //the canvas dimensions
    private GLVec2 canvasDims;
    
    //the shader program that draws the canvas
    private int program;
    
    public GLCPUCanvas(GLVec2 pos, GLVec2 dim, GLVec2 canvasDims, GLColor background) 
    {
    	//program = Main.UI.renderer.getShaderProgram(Shaders.pString);
    	this.pos = pos;
    	this.dim = dim;
    	this.needBuildTexture = true;
    	this.program = Main.UI.renderer.getShaderProgram(Shaders.pImage);
    	this.toUpdate = new LinkedList<Pair<Integer, Integer>>(); 
    	this.canvasTexture = new int[] {-1};
    	this.canvasDims = canvasDims;
    	canvasBuffer = IntBuffer.allocate((int) (canvasDims.x()*canvasDims.y()));
    	cleanBuffer(background);
    }
    
    private void cleanBuffer(GLColor background)
    {
    	for(int i = 0; i < getSizeInDWords(); i++) 
    		canvasBuffer.put(i, background.toInt());
    }
   
    private int getSizeInDWords()
    {
    	return (int) (canvasDims.x() * canvasDims.y());
    }
    
    public void draw(float[] matrix) 
    {
    	if(needBuildTexture) 
    	{
    		buildTexture();
    		needBuildTexture = false;
    	}
    	while(!toUpdate.isEmpty())
    	{
    		updateTextureArea(toUpdate.pollFirst());
    	}
    	
    	int mPositionHandle;
    	int mTexelHandle;
        int mColorHandle;
        int mMVPMatrixHandle;
        int mTextureHandle;
        int texture;
        
        final float[] color = new float[]{1,1,1,1};
        
        Main.UI.renderer.pushMatrix(matrix);
        Matrix.translateM(matrix, 0, pos.x(), pos.y(), 0);
        //Matrix.rotateM(matrix, 0, rotation, 0, 0, 1);
    	Matrix.scaleM(matrix, 0, matrix, 0, dim.x(), dim.y(), 1);
        GLES20.glUseProgram(program);
    	
    	texture = canvasTexture[0];
    	
        if((mPositionHandle = cache.get(1)) < 0)
        	mPositionHandle = cache.set(1, GLES20.glGetAttribLocation(program, "aPosition"));
        
        if((mColorHandle = cache.get(2)) < 0)
        	mColorHandle = cache.set(2, GLES20.glGetUniformLocation(program, "uColor"));
        
        if((mTexelHandle = cache.get(3)) < 0)
        	mTexelHandle = cache.set(3, GLES20.glGetAttribLocation(program, "aTexCoord"));
        
        if((mTextureHandle = cache.get(4)) < 0)
        	mTextureHandle = cache.set(4, GLES20.glGetUniformLocation(program, "uTexture"));
        
        if((mMVPMatrixHandle = cache.get(5)) < 0) 
        	mMVPMatrixHandle = cache.set(5, GLES20.glGetUniformLocation(program, "uMVPMatrix"));
        
        // Enable a handle to the triangle vertices
        GLES20.glEnableVertexAttribArray(mPositionHandle);

        // Prepare the triangle coordinate data
        GLES20.glVertexAttribPointer(
                mPositionHandle, 3,
                GLES20.GL_FLOAT, false,
                12, GLRect.vertexBuffer);
        
        GLES20.glEnableVertexAttribArray(mTexelHandle);
        GLES20.glVertexAttribPointer(
        		mTexelHandle, 2,
                GLES20.GL_FLOAT, false,
                0, GLImage.texCoordBuffer);
        
        GLES20.glUniform4fv(mColorHandle, 1, color, 0);
        
        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texture);
        GLES20.glUniform1i(mTextureHandle, 0);
        
        // Apply the projection and view transformation
        GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, matrix, 0);
        //GLRenderer.checkGLError("glUniformMatrix4fv");

        // Draw the square
        GLES20.glDrawElements(
                GLES20.GL_TRIANGLES, GLRect.drawOrder.length,
                GLES20.GL_UNSIGNED_SHORT, GLRect.drawListBuffer);

        // Disable vertex array
        GLES20.glDisableVertexAttribArray(mPositionHandle);
        GLES20.glDisableVertexAttribArray(mTexelHandle);
    	
        Main.UI.renderer.popMatrix(matrix);
    }

    private void updateTextureArea(Pair<Integer, Integer> linesRange)
    {
    	GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, canvasTexture[0]);
		Log.d("R", linesRange.first +  " " + linesRange.second + " cap: " + canvasBuffer.capacity());
    	canvasBuffer.position(0);
    	Log.d("R", "WOOT");
    	GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, linesRange.first, (int)canvasDims.x(), 1, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, canvasBuffer);
    	Log.d("R", "WOOT2");
    	canvasBuffer.position(0);
    }
    
	private void buildTexture() 
	{
		if(canvasTexture[0] != -1)
		{
			GLES20.glDeleteTextures(1, canvasTexture, 0);
			canvasTexture[0] = -1;
		}
		GLES20.glGenTextures(1, canvasTexture, 0);
		GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, canvasTexture[0]);
		GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST); 
		GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST);
		GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, (int)canvasDims.x(), (int)canvasDims.y(), 0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, canvasBuffer);
		
	}
    
	/**
	 * Set a pixel at given coordinates
	 * @param pos coordinates, where (0,0) is top left corner
	 * @param pxl the color
	 */
    public void putPixel(GLVec2 pos, GLColor pxl)
    {
    	int width = (int) canvasDims.x();
    	int index = (int)pos.x() + width * (int)pos.y();
    	canvasBuffer.put(index, pxl.toInt());
    	toUpdate.add(new Pair<Integer, Integer>((int)pos.y(), (int)pos.y()+1));
    }
    
    /**
	 * Put a rectangle at given coordinates
	 * @param box coordinates, where (0,0) is top left corner
	 * @param color the color
	 */
    public void putRect(GLBoundingBox box, GLColor color)
    {
    	int[] line = new int[(int) box.dim().x()];
    	Arrays.fill(line, color.toInt());
    	int yStart = (int) box.pos().y();
    	int yEnd = (int)(box.pos().y() + box.dim().y());
    	for(int y = yStart; y < yEnd; y++)
    	{
    		canvasBuffer.position(y*(int)canvasDims.x()+(int)box.pos().x());
    		//canvasBuffer.put
    	}
    	toUpdate.add(new Pair<Integer, Integer>(yStart, yEnd));
    }
    
    public GLVec2 getPos() 
	{
		return pos;
	}
    
    public void setPos(GLVec2 pos) 
	{
		this.pos = pos;
	}

}